package com.example.demo.controller;

import cn.dev33.satoken.SaManager;
import cn.dev33.satoken.stp.SaLoginModel;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.aliyun.oss.OSS;
import com.example.demo.configuration.AliyunOssConfig;
import com.example.demo.constant.SystemConstant;
import com.example.demo.controller.bean.Result;
import com.example.demo.dto.RegisterStudentDTO;
import com.example.demo.mapper.entity.Student;
import com.example.demo.mapper.entity.StudentCertification;
import com.example.demo.service.MailService;
import com.example.demo.service.StudentCertificationService;
import com.example.demo.service.StudentLoginService;
import com.example.demo.utils.*;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.util.FileCopyUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

/**
 * @author Lucy
 * @create 2024-01-30 22:03
 */
@RequestMapping("studentCertificationController")
@Api("学生认证接口")
@Slf4j
@RestController
public class StudentCertificationController {

    @Autowired
    private StudentLoginService studentLoginService;

    @Autowired
    private StudentCertificationService service;

    @Autowired
    private MailService mailService;

    @Autowired
    private RedisUtil redisUtil;

    private OSS ossClient;

    @ApiOperation("学生退出学生认证流程")
    @RequestMapping(value = "exitProcess", method = {RequestMethod.POST,  RequestMethod.GET} )
    public Result<Object> exitProcess(){
        //不存在的学生，注：不存在在student表中的新生是无法在前面登录，这里只有一个伪退出
        if (StpUtil.getLoginIdDefaultNull() == null) return Result.fail("退出成功");
        //存在学生
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);
        int result = service.exitProcess(currentStudent);
        if(result == 0) return Result.warn("退出失败");
        StpUtil.logout();
        return Result.ok("退出成功");
    }

    @ApiOperation("学生身份认证")
    @RequestMapping(value = "StudentIdentityVerification", method = {RequestMethod.POST,  RequestMethod.GET} )
    public Result<Object> StudentIdentityVerification(@RequestBody RegisterStudentDTO dto){
        Student currentStudent = studentLoginService.selectStudentByIDCard(dto.getIdentityCard());
        if(currentStudent == null || !currentStudent.getEnrollmentId().equals(dto.getEnrollmentId())) return Result.warn("账号不存在");
        if(currentStudent.getState() != 1) return Result.warn("该账号已认证");
        // 会话登录，参数填登录人的账号id
        StpUtil.login(currentStudent.getId());
        String token = StpUtil.getTokenValue();
        String tokenName = SaManager.getConfig().getTokenName();
        Map<String, Object> map = new HashMap<>();
        map.put("token", token);
        map.put("tokenName", tokenName);
        map.put("studentNumber", currentStudent.getNumber());
        return Result.ok("认证成功",map);
    }

    @ApiOperation(value = "人脸识别")
    @PostMapping("/UploadPortraitImage")
    public Result UploadPortraitImage(MultipartFile file) throws IOException {
        // 获取当前会话账号id
        int studentId = StpUtil.getLoginIdAsInt();
        // 读取图片资源
        ClassPathResource resource = new ClassPathResource("static/faceImage/"+ studentId +".png");
        byte[] fileBytes = FileCopyUtils.copyToByteArray(resource.getInputStream());

        // 将字节数组进行 Base64 编码
        String file1 = Base64.encodeBase64String(fileBytes);

        fileBytes = file.getBytes();
        // 将字节数组进行 Base64 编码
        String file2 = Base64.encodeBase64String(fileBytes);

        String result = FaceMatchUtil.faceMatch(file1, file2);
        JSONObject jsonObject = JSONUtil.parseObj(result);
        if (jsonObject.get("error_msg").equals("SUCCESS")) {
            if(jsonObject.getJSONObject("result").getDouble("score") > 90.0){
                return Result.ok("人脸比对成功");
            } else{
                return Result.warn("人脸相似度低");
            }
        } else if(jsonObject.getInt("error_code") == 223120){
            return Result.warn("活体检验未通过");
        } else{
            return Result.warn("人脸比对失败");
        }
    }

    //提交免冠照片
    @ApiOperation(value = "上传免冠照片")
    @PostMapping("/uploadBareheaded")
    public Result uploadBareheaded(MultipartFile file) throws IOException {
        // 获取当前会话账号id
        int studentId = StpUtil.getLoginIdAsInt();

        Student currentStudent = service.selectStudentById(studentId);

        log.info(file.toString());

        String originalFileName = file.getOriginalFilename();//从上传的文件中获取原始文件名
        int i = originalFileName.lastIndexOf(".");
        String png = originalFileName.substring(i);//获取文件扩展名

        //存储空间的名字
        String bucketName = "seedling-classmate";

        //创建存储对象
        ossClient = AliyunOssConfig.ossClient();

        //设置文件名并上传到OSS
        String fileName = "bareheaded/id-" +studentId+"-"+ UUID.randomUUID().toString().substring(0,12)+png;
        ossClient.putObject(bucketName, fileName, file.getInputStream());   //之后在数据库中保存fileName

        StudentCertification bareheaded = new StudentCertification();
        bareheaded.setStudentNumber(currentStudent.getNumber());
        bareheaded.setBareheadedPhoto(fileName);

        service.placeBareheadedPhoto(bareheaded);
        ossClient.shutdown();

        String result = "http://seedling-classmate.oss-cn-beijing.aliyuncs.com/" + fileName;
        return Result.ok("上传成功", result);
    }

    @ApiOperation(value = "上传身份证头像面")
    @PostMapping("/uploadIdCardFace")
    public Result uploadIdHeadPic(MultipartFile file) throws IOException {
        // 获取当前会话账号id
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);
        int errorNum = 0;
        String key = SystemConstant.certificationState + ":" + currentStudent.getNumber();
        if (redisUtil.hasKey(key)) {
            errorNum = (int) redisUtil.get(key);
        }
        if (errorNum >= SystemConstant.certificationErrorNum)
            return Result.warn("认证错误次数达到上限，申请人工服务");
        long time = SystemConstant.certificationErrorNum_Time;
        log.info(file.toString());

        String originalFileName = file.getOriginalFilename();//从上传的文件中获取原始文件名
        int i = originalFileName.lastIndexOf(".");
        String png = originalFileName.substring(i);//获取文件扩展名

        //存储空间的名字
        String bucketName = "seedling-classmate";

        //创建存储对象
        ossClient = AliyunOssConfig.ossClient();

        //设置文件名
        String fileName = "idCardFace/" + UUID.randomUUID().toString().substring(0, 24) + png;

        //用文件调用百度身份证识别API
        File scratchFile = File.createTempFile("temp", null);
        try (FileOutputStream fos = new FileOutputStream(scratchFile)) {
            fos.write(file.getBytes());
        }
        String judgeResult = IdcardUtil2.judgeIdcardFace(scratchFile);
        JSONObject jsonObject = JSONUtil.parseObj(judgeResult);
        String imageStatus = jsonObject.getStr("image_status");
        if (!imageStatus.equals("normal")) {
            redisUtil.set(key, errorNum + 1, time);
            ossClient.shutdown();
            return PromptUtil.imageStatusPrompt(imageStatus);
        }
        String riskType = jsonObject.getStr("risk_type");
        if (!riskType.equals("normal")) {
            redisUtil.set(key, errorNum + 1, time);
            ossClient.shutdown();
            return PromptUtil.riskTypePrompt(riskType);
        }
        int idcardNumberType = jsonObject.getInt("idcard_number_type");
        if (idcardNumberType != 1) {
            redisUtil.set(key, errorNum + 1, time);
            ossClient.shutdown();
            return PromptUtil.idCardNumberTypePrompt(idcardNumberType);
        }
        String idCardNumber = (String) jsonObject.getJSONObject("words_result").getJSONObject("公民身份号码").get("words");
        if(!currentStudent.getIdentityCard().equals(idCardNumber)){
            redisUtil.set(key, errorNum + 1, time);
            ossClient.shutdown();
            return Result.warn("身份证信息与数据库记录不符，请检查后重新上传。");
        }
        if (errorNum > 0) redisUtil.del(key);

        //把裁剪后的身份证图片上传到OSS
        String card_image = jsonObject.getStr("card_image");
        byte[] imageBytes = Base64Util.base64ToBytes(card_image);
        InputStream inputStream = new ByteArrayInputStream(imageBytes);
        ossClient.putObject(bucketName, fileName, inputStream);

        //在数据库中保存fileNam
        StudentCertification cardFace = new StudentCertification();
        cardFace.setStudentNumber(currentStudent.getNumber());
        cardFace.setIdCardFace(fileName);
        service.placeIdCardFace(cardFace);
        ossClient.shutdown();

        String result = SystemConstant.aliyunOSSUrlPrefix + fileName;
        return Result.ok("上传成功", result);
    }

    @ApiOperation(value = "上传身份证国徽面")
    @PostMapping("/uploadIdCardNational")
    public Result uploadIdCardNational(MultipartFile file) throws IOException {
        // 获取当前会话账号id
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);
        int errorNum = 0;
        String key = SystemConstant.certificationState + ":" + currentStudent.getNumber();
        if (redisUtil.hasKey(key)) {
            errorNum = (int) redisUtil.get(key);
        }
        if (errorNum >= SystemConstant.certificationErrorNum)
            return Result.warn("认证错误次数达到上限，申请人工服务");
        long time = SystemConstant.certificationErrorNum_Time;

        log.info(file.toString());

        String originalFileName = file.getOriginalFilename();//从上传的文件中获取原始文件名
        int i = originalFileName.lastIndexOf(".");
        String png = originalFileName.substring(i);//获取文件扩展名

        //存储空间的名字
        String bucketName = "seedling-classmate";

        //创建存储对象
        ossClient = AliyunOssConfig.ossClient();

        //设置文件名并上传到OSS
        String fileName = "idCardNational/" + UUID.randomUUID().toString().substring(0, 24) + png;

        //用文件地址调用百度身份证识别API
        File scratchFile = File.createTempFile("temp", null);
        try (FileOutputStream fos = new FileOutputStream(scratchFile)) {
            fos.write(file.getBytes());
        }
        String judgeResult = IdcardUtil2.judgeIdcardNational(scratchFile);
        JSONObject jsonObject = JSONUtil.parseObj(judgeResult);
        String imageStatus = jsonObject.getStr("image_status");
        if (!imageStatus.equals("normal")) {
            redisUtil.set(key, errorNum + 1, time);
            ossClient.shutdown();
            return PromptUtil.imageStatusPrompt(imageStatus);
        }
        String riskType = jsonObject.getStr("risk_type");
        if (!riskType.equals("normal")) {
            redisUtil.set(key, errorNum + 1, time);
            ossClient.shutdown();
            return PromptUtil.riskTypePrompt(riskType);
        }
        if (errorNum > 0) redisUtil.del(key);

        //把裁剪后的身份证图片上传到OSS
        String card_image = jsonObject.getStr("card_image");
        byte[] imageBytes = Base64Util.base64ToBytes(card_image);
        InputStream inputStream = new ByteArrayInputStream(imageBytes);
        ossClient.putObject(bucketName, fileName, inputStream);

        //在数据库中保存fileNam
        StudentCertification national = new StudentCertification();
        national.setStudentNumber(currentStudent.getNumber());
        national.setIdCardNational(fileName);
        service.placeIdCardNational(national);
        ossClient.shutdown();

        String result = SystemConstant.aliyunOSSUrlPrefix + fileName;
        return Result.ok("上传成功", result);
    }

    @ApiOperation(value = "上传录取通知书图片")
    @PostMapping("/uploadAdmissionNotice")
    public Result uploadAdmissionNotice(MultipartFile file) throws IOException {
        // 获取当前会话账号id
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);

        log.info(file.toString());

        String originalFileName = file.getOriginalFilename();//从上传的文件中获取原始文件名
        int i = originalFileName.lastIndexOf(".");
        String png = originalFileName.substring(i);//获取文件扩展名

        //存储空间的名字
        String bucketName = "seedling-classmate";

        //创建存储对象
        ossClient = AliyunOssConfig.ossClient();

        //设置文件名并上传到OSS
        String fileName = "admissionNotice/" + UUID.randomUUID().toString().substring(0,24)+png;
        ossClient.putObject(bucketName, fileName, file.getInputStream());//之后在数据库中保存fileNam

        StudentCertification admission = new StudentCertification();
        admission.setStudentNumber(currentStudent.getNumber());
        admission.setAdmissionNotice(fileName);

        service.placeAdmissionNotice(admission);
        ossClient.shutdown();

        String result = SystemConstant.aliyunOSSUrlPrefix + fileName;
        return Result.ok("上传成功", result);

    }

    @ApiOperation("学生通过手机号获取验证码")
    @RequestMapping(value = "getCodeByPhone", method = {RequestMethod.POST,  RequestMethod.GET} )
    public Result<Object> getCodeByPhone(@RequestParam("phone")String phone){
        if (StpUtil.getLoginIdDefaultNull() == null) Result.fail("获取验证码失败,返回上一个页面");
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);
        if (currentStudent == null) return Result.warn("账号不存在");
        if (service.selectPhoneCount(phone) > 0) return Result.warn("重复电话号码");
        String code = CodeUtil.generateCode(6);
        String key = "PHONECODE" + ":" + phone;
        long time = SystemConstant.codeExpiration_Time;
        redisUtil.set(key, code, time);
        SMSUtil.sendCode(phone, code);
        return Result.ok("验证码已发送");
    }

    @ApiOperation("判断学生提交的手机验证码是否正确")
    @RequestMapping(value = "judgePhoneCode", method = {RequestMethod.POST,  RequestMethod.GET} )
    public Result<Object> judgePhoneCode(@RequestBody RegisterStudentDTO dto){
        if (StpUtil.getLoginIdDefaultNull() == null) Result.fail("提交失败,返回上一个页面");
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);
        int errorNum = 0;
        String key = SystemConstant.userState + ":" + currentStudent.getNumber();
        if (redisUtil.hasKey(key)) {
            errorNum = (int) redisUtil.get(key);
        }
        if (errorNum >= SystemConstant.codeErrorNum)
            return Result.warn("此账号因验证码错误次数太对，已经被暂时禁用。");
        String phoneCodeKey = "PHONECODE" + ":" + dto.getPhone();
        String phoneCode = "";
        if (redisUtil.hasKey(phoneCodeKey)) {
            phoneCode = (String) redisUtil.get(phoneCodeKey);
        }else{
            return Result.warn("手机号验证码已过期，请重新获取验证码");
        }
        if (!dto.getPhoneCode().equals(phoneCode)){
            long time = SystemConstant.codeErrorNum_Time;
            redisUtil.set(key, errorNum + 1, time);
            return Result.warn("手机号验证码错误");
        }
        if (errorNum > 0) redisUtil.del(key);
        currentStudent.setPhone(dto.getPhone());
        int result = service.updateStudentPhone(currentStudent);
        if(result == 0) return Result.warn("提交失败");
        return Result.ok("提交成功");
    }

    @ApiOperation("学生通过邮箱获取验证码")
    @RequestMapping(value = "getCodeByEmail", method = {RequestMethod.POST,  RequestMethod.GET} )
    public Result<Object> getCodeByEmail(@RequestParam("email")String email){
        if (StpUtil.getLoginIdDefaultNull() == null) Result.fail("获取验证码失败,返回上一个页面");
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);
        if (currentStudent == null) return Result.warn("账号不存在");
        if (service.selectEmailCount(email) > 0) return Result.warn("重复邮箱");
        String code = CodeUtil.generateMixCode(6);
        String key = "EMAILCODE" + ":" + email;
        long time = SystemConstant.codeExpiration_Time;
        redisUtil.set(key, code, time);
        // 发送验证码邮件
        try {
            mailService.sendCodeMail(email,code);
        } catch (Exception e) {
            e.printStackTrace();
            return Result.warn("获取验证码失败，再次点击获取验证码按钮");
        }
        return Result.ok("验证码已发送");
    }

    @ApiOperation("判断学生提交的邮箱验证码是否正确")
    @RequestMapping(value = "judgeEmailCode", method = {RequestMethod.POST,  RequestMethod.GET} )
    public Result<Object> judgeEmailCode(@RequestBody RegisterStudentDTO dto){
        if (StpUtil.getLoginIdDefaultNull() == null) Result.fail("提交失败,返回上一个页面");
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);
        int errorNum = 0;
        String key = SystemConstant.userState + ":" + currentStudent.getNumber();
        if (redisUtil.hasKey(key)) {
            errorNum = (int) redisUtil.get(key);
        }
        if (errorNum >= SystemConstant.codeErrorNum)
            return Result.warn("此账号因验证码错误次数太对，已经被暂时禁用。");
        String emailCodeKey = "EMAILCODE" + ":" + dto.getEmail();
        String emailCode = "";
        if (redisUtil.hasKey(emailCodeKey)) {
            emailCode = (String) redisUtil.get(emailCodeKey);
        }else{
            return Result.warn("邮箱验证码已过期，请重新获取验证码");
        }
        if (!dto.getEmailCode().equals(emailCode)){
            long time = SystemConstant.codeErrorNum_Time;
            redisUtil.set(key, errorNum + 1, time);
            return Result.warn("邮箱验证码错误");
        }
        if (errorNum > 0) redisUtil.del(key);
        currentStudent.setEmail(dto.getEmail());
        int result = service.updateStudentEmail(currentStudent);
        if(result == 0) return Result.warn("提交失败");
        return Result.ok("提交成功");
    }

    @ApiOperation("学生首次设置登录密码")
    @RequestMapping(value = "setPassword1", method = {RequestMethod.POST,  RequestMethod.GET} )
    public Result<Object> setPassword1(@RequestBody RegisterStudentDTO dto){
        int studentId = StpUtil.getLoginIdAsInt();
        Student currentStudent = service.selectStudentById(studentId);
        currentStudent.setNickname();
        currentStudent.setPassword(MD5Util.encryptPassword(dto.getPassword()));
        int result = studentLoginService.setPassword1(currentStudent);
        if(result == 0) return Result.fail("设置失败");
        currentStudent.setState(2);
        service.updateStudentState(currentStudent);
        return Result.ok("设置成功");
    }
}
